﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
using System.Reflection;
using NewsMine.DomainObjects;
using System.Runtime.Serialization.Formatters.Binary;
using FileBasedObjectRepository;

namespace FeedRepositoryTest
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {

            GenericFileDataStore<WebSiteInfo> store = new GenericFileDataStore<WebSiteInfo>("C:\\temp\\Data\\");

            var webSite = store.Store(new WebSiteInfo { Name = "Test", Domain = "test.com" });
 
            var retrievedObject = store.GetById<WebSiteInfo>(webSite.ID);

            Assert.AreEqual(webSite.ID, retrievedObject.ID);


            retrievedObject.Name = "Changed name of the web site";
            store.Store(retrievedObject);

            var retrievedObject1 = store.GetById<WebSiteMenu>(webSite.ID);

            Assert.AreEqual(retrievedObject.Name, retrievedObject1.Name);


            GenericFileDataStore<WebSiteMenu> store1 = new GenericFileDataStore<WebSiteMenu>("C:\\temp\\Data\\");

            var webSiteMenu = store1.Store(new WebSiteMenu { Name = "Test" });

            var retrievedMenuObject = store.GetById<WebSiteMenu>(webSiteMenu.ID);

            Assert.AreEqual(webSiteMenu.ID, retrievedMenuObject.ID);
        }
    }




    public class GenericFileDataStore1<T>
    {
        #region Constructor and directory permission checks
        public string BaseDirectory { get; set; }

        public GenericFileDataStore1(string baseDirectory)
        {
            if (Directory.Exists(baseDirectory))
            {
                if (!IsWritePermissionIsThere(baseDirectory))
                {
                    throw new ApplicationException("Directory is not having write permission.");
                }

            }
            else
            {
                Directory.CreateDirectory(baseDirectory);
            }

            this.BaseDirectory = baseDirectory;

        }

        private bool IsWritePermissionIsThere(string baseDirectory)
        {

            if (File.Exists(Path.Combine(baseDirectory, "testFile.txt")))
                return true;
            else
            {
                try
                {
                    File.WriteAllText(Path.Combine(baseDirectory, "testFile.txt"), "testing write permission");
                }
                catch (IOException ioException)
                {
                    return false;
                }

                return true;
            }
        }

        #endregion

        #region public API

        public T Store(T objToAddOrUpdate)
        {
            return (T)StoreObject(objToAddOrUpdate);
        }

        public object StoreObject(object objToAddOrUpdate)
        {
            Type myType = objToAddOrUpdate.GetType();
            IList<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());


            //Look out for the identity field and set if the object is new

            long identityFieldValue = 0;


            foreach (PropertyInfo prop in props)
            {
                if (prop.Name.ToLower() == "id")
                {
                    object propValue = prop.GetValue(objToAddOrUpdate, null);
                    Type propType = prop.PropertyType;

                    if (propType == typeof(int) || propType == typeof(long))
                    {
                        if (propValue.ToString() == "0")
                        {
                            prop.SetValue(objToAddOrUpdate, GetNextIdentityValue(objToAddOrUpdate), null);
                        }
                    }
                    else
                    {
                        //this is exceptional case. ... id always should be a numeric datatype.
                    }

                    identityFieldValue = (long)prop.GetValue(objToAddOrUpdate, null);

                }

                // Do something with propValue
            }


            //based on the identity field value and object type, need to find the target folder and file name to serialize the object.

            CreateDirectoryForObjectStore(objToAddOrUpdate);

            StoreObjectToStore(objToAddOrUpdate, identityFieldValue);

            //todo: if the object is contains to have reference of non-primitive types, we just need to save them in the db as as well.

            return objToAddOrUpdate;
        }

        public bool DeleteObject(object objToDelete)
        {
            return false;
        }

        public T GetById(long identityFieldValue)
        {
            return (T)GetObjectById(identityFieldValue);
        }

        public List<T> GetObjects(long startIndex, long length)
        {
            //todo: New
            return new List<T>();
        }

        public object GetObjectById(long identityFieldValue)
        {
            object savedObject = DeserializeFromFile(GetTargetFileByObject(new WebSiteInfo { ID = identityFieldValue }));

            return savedObject;
        }

        public List<object> Search(object templateObj)
        {
            return null;
        }

        #endregion


        #region Helper methods

        private long GetIdentityFieldFromObject(object objToAddOrUpdate)
        {
            Type myType = objToAddOrUpdate.GetType();
            IList<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());


            //Look out for the identity field and set if the object is new

            long identityFieldValue = -1;


            foreach (PropertyInfo prop in props)
            {
                if (prop.Name.ToLower() == "id")
                {
                    identityFieldValue = (long)prop.GetValue(objToAddOrUpdate, null);
                }
            }


            return identityFieldValue;
        }

        private void StoreObjectToStore(object objToAddOrUpdate, long identityFieldValue)
        {
            string targetFileNameForObjectDataStore = GetTargetFileByObject(objToAddOrUpdate);

            SerializeObjectToFile(targetFileNameForObjectDataStore, objToAddOrUpdate);
        }

        private void SerializeObjectToFile(string targetFile, object objectToStore)
        {
            Stream stream = File.Open(targetFile, FileMode.Create);
            BinaryFormatter bformatter = new BinaryFormatter();


            bformatter.Serialize(stream, objectToStore);
            stream.Close();
        }

        private object DeserializeFromFile(string fileName)
        {
            object mp = null;

            BinaryFormatter bformatter = new BinaryFormatter();

            //Open the file written above and read values from it.
            Stream stream = File.Open(fileName, FileMode.Open);

            bformatter = new BinaryFormatter();

            mp = bformatter.Deserialize(stream);
            stream.Close();

            return mp;
        }

        private string GetTargetFileByObject(object objToAddOrUpdate)
        {
            string subDirectoryForObjectStore = GetSubDirectoryForObjectStore(objToAddOrUpdate);

            return Path.Combine(subDirectoryForObjectStore, GetIdentityFieldFromObject(objToAddOrUpdate).ToString() + ".dat");
        }

        private string GetSubDirectoryForObjectStore(object objToAddOrUpdate)
        {
            string subDirectoryForObjectStore = Path.Combine(this.BaseDirectory, objToAddOrUpdate.GetType().Name);

            long identityFieldValue = GetIdentityFieldFromObject(objToAddOrUpdate);

            int subFolderBasedOnIdentityNumber = (int)identityFieldValue / 100;

            subDirectoryForObjectStore = Path.Combine(subDirectoryForObjectStore, subFolderBasedOnIdentityNumber.ToString());

            return subDirectoryForObjectStore;
        }

        private void CreateDirectoryForObjectStore(object objToAddOrUpdate)
        {
            string subDirectoryForObjectStore = GetSubDirectoryForObjectStore(objToAddOrUpdate);

            if (!Directory.Exists(subDirectoryForObjectStore))
            {
                Directory.CreateDirectory(subDirectoryForObjectStore);
            }
        }

        #region Identity related mechanism

        string IdentityInfoFilePath
        {
            get
            {
                return Path.Combine(BaseDirectory, "IdentityInfo.dat");
            }
        }


        public Dictionary<string, long> _identityDictionary;

        public Dictionary<string, long> IdentityDictionary
        {
            get
            {
                if (_identityDictionary == null)
                {
                    _identityDictionary = GetIdentifyInformationFromFile(IdentityInfoFilePath);

                    if (_identityDictionary == null)
                    {
                        _identityDictionary = new Dictionary<string, long>();
                        SerializeObjectToFile(IdentityInfoFilePath, _identityDictionary);
                    }
                }

                return _identityDictionary;
            }
        }

        private Dictionary<string, long> GetIdentifyInformationFromFile(string fileName)
        {
            if (!File.Exists(fileName))
                return null;

            return (Dictionary<string, long>)DeserializeFromFile(fileName);
        }

        private object GetNextIdentityValue(object objToAddOrUpdate)
        {
           // return new Random { }.Next(10000);

            //Here we need to store the identity value for each and every clr type.
            string key = objToAddOrUpdate.GetType().FullName;
            long nextValue = 1;
            if (!IdentityDictionary.Keys.Contains<string>(key))
            {
                IdentityDictionary.Add(key, nextValue);
            }
            else
            {
                nextValue =IdentityDictionary[key] + 1;
                IdentityDictionary[key] = nextValue;
            }

            SerializeObjectToFile(IdentityInfoFilePath, IdentityDictionary);

            return nextValue;

        }

        #endregion


        public static T GetPropertyValue<T>(object o, string propertyName)
        {
            return (T)o.GetType().GetProperty(propertyName).GetValue(o, null);
        }

        #endregion

    }




}
